#include "systeminfomation.h"
#include <QSysInfo>
#include <algorithm>
#include <QCryptographicHash>
#include <QUuid>
#include <QDateTime>
#ifdef Q_OS_WIN
#include <windows.h>
#include <netlistmgr.h>
#include <atlbase.h>
#include <Iphlpapi.h>
#include <iostream>
#include <DXGI.h>
#include <vector>

std::string WStringToString(const std::wstring &wstr)
{
       std::string str(wstr.length(), ' ');
       std::copy(wstr.begin(), wstr.end(), str.begin());
      return str;
}

#else
//#import <IOKit/IOKitLib.h>
//#include <CoreFoundation/CoreFoundation.h>
#import<IOKit/IOKitLib.h>
#endif

QString SystemInfomation::m_sUuid;
quint64 SystemInfomation::m_timeStamp;

SystemInfomation::SystemInfomation()
{

}
SystemInfomation::~SystemInfomation()
{

}

QString SystemInfomation::get_unique_id()
{
    std::vector<unsigned char> unique;

#ifdef Q_OS_WIN
    // On Windows, get the MAC address of a network adaptor (preferably Ethernet
    // or IEEE 802.11 wireless

    DWORD dwBufLen = sizeof(IP_ADAPTER_INFO);
    PIP_ADAPTER_INFO AdapterInfo = (PIP_ADAPTER_INFO)malloc(dwBufLen);

    if (GetAdaptersInfo(AdapterInfo, &dwBufLen) == ERROR_BUFFER_OVERFLOW) {
        free(AdapterInfo);
        AdapterInfo = (IP_ADAPTER_INFO*)malloc(dwBufLen);
    }
    if (GetAdaptersInfo(AdapterInfo, &dwBufLen) == NO_ERROR) {
        const IP_ADAPTER_INFO* pAdapterInfo = AdapterInfo;
        std::vector<std::vector<unsigned char>> macs;
        bool ethernet_seen = false;
        while (pAdapterInfo) {
            macs.emplace_back();
            for (unsigned char i = 0; i < pAdapterInfo->AddressLength; ++i)
                macs.back().emplace_back(pAdapterInfo->Address[i]);
            // Prefer Ethernet and IEEE 802.11 wireless
            if (! ethernet_seen) {
                if ((pAdapterInfo->Type == MIB_IF_TYPE_ETHERNET && (ethernet_seen = true))
                 ||  pAdapterInfo->Type == IF_TYPE_IEEE80211)
                    std::swap(macs.front(), macs.back());
            }
            pAdapterInfo = pAdapterInfo->Next;
        }
        if (! macs.empty())
            unique = macs.front();
    }
    free(AdapterInfo);
#else
    constexpr int buf_size = 100;
    char buf[buf_size] = "";
    memset(&buf, 0, sizeof(buf));
    io_registry_entry_t ioRegistryRoot = IORegistryEntryFromPath(kIOMasterPortDefault, "IOService:/");
    if (ioRegistryRoot != MACH_PORT_NULL) {
        CFStringRef uuidCf = (CFStringRef)IORegistryEntryCreateCFProperty(ioRegistryRoot, CFSTR(kIOPlatformUUIDKey), kCFAllocatorDefault, 0);
        IOObjectRelease(ioRegistryRoot);
        CFStringGetCString(uuidCf, buf, buf_size, kCFStringEncodingMacRoman);
        CFRelease(uuidCf);
    }
    // Now convert the string to std::vector<unsigned char>.
    for (char* c = buf; *c != 0; ++c)
        unique.emplace_back((unsigned char)(*c));
#endif

    // In case that we did not manage to get the unique info, just return an empty
    // string, so it is easily detectable and not masked by the hashing.
    if (unique.empty())
        return "";

    uint64_t prime = 20230607145300;
    size_t beg = unique.size();
    unique.resize(beg + 8);
    memcpy(&unique[beg], &prime, 8);
    std::string strUnique;
    strUnique.insert(strUnique.begin(), unique.begin(), unique.end());

    QString Md5Arr;
    QByteArray cryptArr;
    QCryptographicHash md(QCryptographicHash::Md5);
    md.addData(strUnique.c_str(), strUnique.size());
    cryptArr = md.result();
    Md5Arr.append(cryptArr.toHex());
    qDebug() << "uuid: " << Md5Arr;
    return Md5Arr;
}

#ifdef Q_OS_WIN
QString SystemInfomation::get_cpu_info_from_registry()
{
    std::string cpuInfomation, coreInfo, modelInfo, vendorInfo;
    int idx = -1;
    constexpr DWORD bufsize_ = 500;
    DWORD bufsize = bufsize_-1; // Ensure a terminating zero.
    char buf[bufsize_] = "";
    memset(buf, 0, bufsize_);
    const std::string reg_dir = "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\";
    std::string reg_path = reg_dir;

    // Look into that reg dir and possibly into subdirs called 0, 1, 2, etc.
    // If the latter, count them.

    while (true) {
        if (RegGetValueA(HKEY_LOCAL_MACHINE, reg_path.c_str(), "ProcessorNameString",
            RRF_RT_REG_SZ, NULL, &buf, &bufsize) == ERROR_SUCCESS) {
            coreInfo = buf;
            modelInfo = std::to_string((std::max)(1, idx + 1));
            if (RegGetValueA(HKEY_LOCAL_MACHINE, reg_path.c_str(),
                "VendorIdentifier", RRF_RT_REG_SZ, NULL, &buf, &bufsize) == ERROR_SUCCESS)
                vendorInfo = buf;
        }
        else {
            if (idx >= 0)
                break;
        }
        ++idx;
        reg_path = reg_dir + std::to_string(idx) + "\\";
        bufsize = bufsize_-1;
    }

    cpuInfomation = coreInfo + " ### " + modelInfo + " ### " + vendorInfo;
    return QString::fromStdString(cpuInfomation);
}
#else // Apple
QString SystemInfomation::get_cpu_info_from_registry()
{
    QString cpuInfomation;
    std::map<std::string, std::string> sysctl = parse_lscpu_etc("sysctl -a", ':');
    cpuInfomation += QString::fromStdString(sysctl["hw.ncpu"]);
    cpuInfomation += QString::fromStdString(sysctl["machdep.cpu.brand_string"]);
    cpuInfomation += QString::fromStdString(sysctl["machdep.cpu.vendor"]);
    return cpuInfomation;
}

std::string& trim(std::string &s) {
    if (s.empty()) {
        return s;
    }

    s.erase(0,s.find_first_not_of(" "));
    s.erase(s.find_last_not_of(" ") + 1);
    return s;
}

std::string SystemInfomation::parse_rame_etc(const std::string& name)
{
    std::string out = "";
    constexpr size_t max_len = 1000;
    char cline[max_len] = "";
    FILE* fp = popen(name.data(), "r");
    if (fp != NULL) {
        while (fgets(cline, max_len, fp) != NULL) {
            std::string line(cline);
            line.erase(std::remove_if(line.begin(), line.end(),
                [](char c) { return c == '\"' || c == '\r' || c == '\n'; }),
                line.end());
            out = line;
        }
        pclose(fp);
    }

    return out;
}

std::map<std::string, std::string> SystemInfomation::parse_lscpu_etc(const std::string& name, char delimiter)
{
    std::map<std::string, std::string> out;
    constexpr size_t max_len = 1000;
    char cline[max_len] = "";
    FILE* fp = popen(name.data(), "r");
    if (fp != NULL) {
        while (fgets(cline, max_len, fp) != NULL) {
            std::string line(cline);
            line.erase(std::remove_if(line.begin(), line.end(),
                [](char c) { return c == '\"' || c == '\r' || c == '\n'; }),
                line.end());
            size_t pos = line.find(delimiter);
            if (pos < line.size() - 1) {
                std::string key = line.substr(0, pos);
                std::string value = line.substr(pos + 1);
                trim(key); // remove leading and trailing spaces
                trim(value);
                out[key] = value;
            }
        }
        pclose(fp);
    }
    return out;
}
#endif

QString SystemInfomation::get_ram_info()
{
#ifdef Q_OS_WIN
    MEMORYSTATUSEX memoryStatus;
    memoryStatus.dwLength = sizeof(MEMORYSTATUSEX);
    GlobalMemoryStatusEx(&memoryStatus);
    double totalPhysMem = ((memoryStatus.ullTotalPhys  / 1024 / 1024)  * 100)/ 1024 / 100.0;
    QString ramInfo = QString::number(totalPhysMem, 'f', 2) + " G";
    qDebug() << "ram: " << ramInfo;
    return ramInfo;
#else
    std::map<std::string, std::string> sysctl = parse_lscpu_etc("sysctl hw", ':');
    long long ramNumber = QString::fromStdString(sysctl["hw.memsize"]).toLongLong();
    double totalPhysMem = ((ramNumber / 1024 / 1024)  * 100)/ 1024 / 100.0;
    QString ramInfo = QString::number(totalPhysMem, 'f', 2) + " G";
    qDebug() << "ram: " << ramInfo;
    return ramInfo;
#endif
}

#ifdef Q_OS_WIN
    typedef NTSTATUS( NTAPI* fnRtlGetVersion )(PRTL_OSVERSIONINFOW lpVersionInformation);
#endif

QString SystemInfomation::get_os_info()
{
    QString winName = QSysInfo::prettyProductName();
#ifdef Q_OS_WIN
    OSVERSIONINFOW verInfo;
    static auto RtlGetVersion = (fnRtlGetVersion)GetProcAddress( GetModuleHandleW( L"ntdll.dll" ), "RtlGetVersion" );
    if (RtlGetVersion != 0 && RtlGetVersion( (PRTL_OSVERSIONINFOW)&verInfo ) == 0)
    {
        if(verInfo.dwBuildNumber >= 22000)
            winName = "windows 11 or upper";
    }
#endif
    QString osInfo = winName + " ### " + QSysInfo::kernelVersion() + " ### " \
            + QSysInfo::buildAbi();
    qDebug() << "os: " << osInfo;
    return osInfo;
}

QString SystemInfomation::get_display_info()
{
    QString gpuName = "";
#ifdef Q_OS_WIN
    qDebug() << "cpu: " << get_cpu_info_from_registry();
    IDXGIFactory* pFactory = nullptr;
    IDXGIAdapter* pAdapter = nullptr;
    std::vector <IDXGIAdapter*> vAdapters;
    // 显卡的数量
    int iAdapterNum = 0;
    // 创建一个DXGI工厂
    HRESULT hr = CreateDXGIFactory(__uuidof(IDXGIFactory), (void**)(&pFactory));
    if (FAILED(hr))
    return gpuName;

    while (pFactory->EnumAdapters(iAdapterNum, &pAdapter) != DXGI_ERROR_NOT_FOUND)
    {
        vAdapters.push_back(pAdapter);
        ++iAdapterNum;
    }

    for (size_t i = 0; i < vAdapters.size(); i++)
    {
        // 获取信息
        DXGI_ADAPTER_DESC adapterDesc;
        vAdapters[i]->GetDesc(&adapterDesc);
        std::wstring aa(adapterDesc.Description);
        gpuName = QString::fromStdWString(aa);

        double shareGpuMem = ((adapterDesc.SharedSystemMemory  / 1024 / 1024)  * 100)/ 1024 / 100.0;
        QString gpuInfo = QString::number(shareGpuMem, 'f', 2) + " G";
        gpuName += " ### " + gpuInfo;
        qDebug() << "gpu: " << gpuName;
        break;
    }

    if(gpuName.isEmpty())
        gpuName = "N/A";

    vAdapters.clear();
    return gpuName;
#else

    std::map<std::string, std::string> sysctl = parse_lscpu_etc("system_profiler SPDisplaysDataType | grep VRAM", ':');
    if(sysctl.size() > 0)
        gpuName += QString::fromStdString(sysctl.begin()->second);
    sysctl.clear();
    sysctl = parse_lscpu_etc("system_profiler SPDisplaysDataType | grep Chipset", ':');
    if(sysctl.size() > 0)
        gpuName += " ### " + QString::fromStdString(sysctl.begin()->second);
    if(gpuName.isEmpty())
        gpuName = "N/A";
#endif

    return gpuName;
}

QString SystemInfomation::getVersion()
{
    return "V3.0.0";
}

QString SystemInfomation::getCipher()
{
    return "ANYCUBIC";
}

QString SystemInfomation::genTimeStamp()
{
    // 获取当前时间戳
    QDateTime timeDate = QDateTime::currentDateTime(); // 获取当前时间
    uint timeStr = timeDate.toTime_t(); // 将当前时间转为时间戳(ms)
    return QString::number(timeStr) + "000";
}

QString SystemInfomation::get_signature(const QString& timpeStamp, const QString& uuid)
{
    QString appId = "f9b3528877c94d5c9c5af32245db46ef";
    QString appSecret = "0cf75926606049a3937f56b0373b99fb";
    QString version = getVersion();
    QString encryptedSource =
        appId + timpeStamp + version + appSecret + uuid + appId;
    QByteArray hashData = QCryptographicHash::hash(encryptedSource.toUtf8(),
                                                   QCryptographicHash::Md5);
    QString md5Str = hashData.toHex();
    return md5Str;
}

QString SystemInfomation::get_nonce()
{
    return QUuid::createUuid().toString().remove('}').remove('{');
}

